import ssl
import hyper
from threading import Thread,Lock,enumerate
import datetime
import os, sys, stat  
import uuid
import time
import io
import util
import hashlib
import configparser
from urllib import parse
from queue import Queue
import base64
import ctypes
import inspect
import time

class AccessRecord():
    device_id=0 
    person_id=0
    device_key=None
    score=0
    pass_result=2 #0通过,1未通过,2未知
    scene_images=None #抓拍的场景图
    access_time=None
    scene_face_images=None #抓拍的人脸图
    itype=None #2通行记录 1抓拍记录

class RequestError(Exception):
    def __init__(self,status,*args:object):
        super().__init__(args)
        self.status=status

class OperationType:
    ADD="ADD"
    REMOVE="REMOVE"
    MODIFY="MODIFY"

class DEVICE_YT:
    port=8443
    OPEN_CLOSE="OPEN_CLOSE"#打开后关闭
    ALWAYS_OPEN="ALWAYS_OPEN"#长开
    ALWAYS_CLOSE="ALWAYS_CLOSE"#长关
    checkpoint=0

    re_conn_number=0

    LOG_OPERATION="operation" #操作
    LOG_ERROR="error" #出错

    def __init__(self,info,id,host,username,password,log_callBack,checkpoint=0,threshold=85,save_image_path=r"image"):
        self.save_image_path=save_image_path #过人记录图片保存目录
        self.host=host #ip
        self.username=username
        self.password=password
        self.id=id #device_id
        self.info=info #设备信息

        self.access_records_thread=None #过人记录线程
        self.sync_person_thread=None #同步人员线程
        self.threshold=threshold
        self.lock=Lock()

        self.lock_record=Lock()

        self.access_records_http20=None #过人记录连接对象
        self.access_records_last_time=None #上次有数据的时间 超过15秒要重新连接
        self.access_records_call_Back=None #过人记录回调

        self.checkpoint=checkpoint
        self.__log_callBack=log_callBack
        self.__queue_person=Queue() #需要同步的人员队列
        self.retry_conn=0 #重新连接的次数
        self.exit=False #退出
        self.state=None #连接状态 
        self.state_text="未知"

    def call_exit(self):
        self.exit=True
        if(self.access_records_http20!=None):
            self.access_records_http20["response"].close()
            self.access_records_http20["conn"].close()
            self.access_records_http20=None
            
        self.__log_callBack(self,"正在退出所有连接",self.LOG_OPERATION)
        self.sync_person_thread.join()
        self.access_records_thread.join()
        self.sync_person_thread=None
        self.access_records_thread=None
        self.state=False
        self.__log_callBack(self,"成功退出所有连接",self.LOG_OPERATION)
        
        return True

    def put_person(self,pname,device_person_id,person_link_device_id,mode,cardNo=None,byDisplayPic=None):
        """
            mode 0添加 1修改 2删除
            推送一个需要同步的人到队列
        """
        if(self.state==True):
            if(mode==0):
                operationType=OperationType.ADD
            elif(mode==1):
                operationType=OperationType.MODIFY
            else:
                operationType=OperationType.REMOVE

            self.__queue_person.put({
                "pname":pname,
                "person_link_device_id":person_link_device_id,
                "device_person_id":device_person_id,
                "cardNo":cardNo,
                "displayPic":byDisplayPic,
                "operationType":operationType,
            })
        return self.state

    def _async_raise(self,tid, exctype):
        """raises the exception, performs cleanup if needed"""
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def stop_access_records_thread(self):
        print("线程数量",len(enumerate()))
        self._async_raise(self.access_records_thread.ident, SystemExit)
        self.access_records_thread=None
        self.access_records(self.access_records_call_Back)

    def __th_sync_person(self,callBack):
        while True:
            if(self.exit):
                break
            if(self.access_records_last_time!=None):
                if((time.time()-self.access_records_last_time)>600):
                    if(self.access_records_http20!=None):
                        self.access_records_last_time=time.time()
                        self.lock.acquire()
                        self.access_records_http20["response"].close()
                        self.access_records_http20["conn"].close()
                        self.access_records_http20=None
                        self.lock.release()
                        print("结束线程")
                        # self.stop_access_records_thread()

            if not self.__queue_person.empty():
                item=self.__queue_person.get()
                try:
                    # if(item["mode"]==0):
                    #     self.save_person(item["device_person_id"],item["cardNo"],item["displayPic"],True,item["pname"])
                    # elif(item["mode"]==1):
                    self.save_person(item["device_person_id"],item["cardNo"],item["displayPic"],item["operationType"],item["pname"])
                    # else:
                    #     self.delete_person(item["device_person_id"])
                    data={"sync_state":2,"sync_Result":"成功","person_link_device_id":item["person_link_device_id"],"device_person_id":item["device_person_id"]}
                except RequestError as e_request:
                    data={"sync_state":3,"sync_Result":e_request,"person_link_device_id":item["person_link_device_id"]}
                except Exception as e:
                    data={"sync_state":3,"sync_Result":e,"person_link_device_id":item["person_link_device_id"]}
                callBack(self,data)
            time.sleep(3/1000)

    def sync_person(self,callBack):
        """
            启动同步人员线程
        """
        if(self.sync_person_thread==None):
            self.sync_person_thread=Thread(target=self.__th_sync_person,args=(callBack,))
            self.sync_person_thread.start()
    
    def __http20(self,path,headers,method="GET",body=None,ERROR_SCHEMA=None,timeout=60):
        """
            获取请求对象
        """
        if(self.exit):
            return None
        ssl_context=ssl._create_unverified_context()
        conn = hyper.HTTP20Connection(self.host, self.port,
                                        secure=True,ssl_context=ssl_context,force_proto=hyper.tls.H2C_PROTOCOL,timeout=timeout
                                    )
        try:
            conn.request(method, path,body,headers=headers)
            response = conn.get_response()
        except Exception as e:
            self.state=False
            self.state_text=repr(e)
            raise Exception(self.state_text)

        status=response.status
        if(status==200):
            self.retry_conn=0
            self.re_conn_number=0
            self.state=True
            self.state_text="在线"
            return {"response":response,"conn":conn}
        else:
            package_len = int.from_bytes(response.read(4), byteorder='big')
            data="无数据返回"
            if(package_len>0):
                data = response.read(package_len)
                try:
                    data=data.decode(encoding = "utf-8")
                    #with io.BytesIO(data) as f:
                        #data =  util.read_avro(util.ERROR_SCHEMA,util.ERROR_SCHEMA, f)
                except:
                    pass
            response.close()
            conn.close()
            if(status==207):
                raise RequestError(status,"组合状态，Batch操作部分成功。"+data)
            elif(status==400):
                raise RequestError(status,"无效的请求。"+data)
            elif(status==401):
                self.state=False
                self.state_text="用户名密码错误。"+data
                raise RequestError(status,self.state_text)
            elif(status==403):
                raise RequestError(status,"资源不可用。"+data)
            elif(status==404):
                raise RequestError(status,"链接不存在。"+data)
            else:
                self.state=False
                self.state_text="status:{}({})".format(status,data)
                raise Exception(self.state_text)
    
    def __public_headers(self):
        """
            获取公共http头
        """
        headres= {
                "Accept":"application/avro",
                "x-kassq-sg-date":"%d" % time.time(),
                "x-kassq-sg-username":self.username,
                "x-kassq-sg-password":self.password,
                "x-kassq-sg-version":"V1.0",
                "include_picture":"true",
                "x-yitu-sg-username":self.username,
                "x-yitu-sg-password":self.password,
                # "host":"{}:{}".format(self.host, self.port),
                # "null":"null"
                }
        return headres
    
    def __conn_access_records_http20(self):
        # path="/fetch_access_records?response=stream&checkpoint={}&access_records_type=include_image".format(self.checkpoint)
        path="/access_records?response=stream&checkpoint={}&access_records_type=include_image&from_timestamp=0".format(self.checkpoint)
        while True:
            if(self.exit):
                break
            try:
                if(self.access_records_http20!=None):
                    self.access_records_http20["response"].close()
                    self.access_records_http20["conn"].close()
                self.access_records_http20=self.__http20(path,self.__public_headers(),timeout=90)
                break
            except Exception as e:
                self.access_records_http20=None
                self.state=False
                self.state_text=e
                self.__log_callBack(self,"[过人记录线程]{}".format(e),self.LOG_ERROR)
                self.re_conn_number+=1
                time.sleep(self.re_conn_number*3)
        print("链接成功")

    def __handle_access_records(self,data):
        """
        处理过人数据
        """
        base64_face_image=""
        with io.BytesIO(data) as f:
            datum = util.read_avro(util.ACCESS_RECORDS_SCHEMA,util.ACCESS_RECORDS_SCHEMA, f)
            accessRecord=datum["accessRecord"]
            self.checkpoint=datum["checkpoint"]
            result={"personId":None,"host":self.host}
            for key in accessRecord:
                # personId=accessRecord["personId"]
                if(key=="imagesList"):
                    for image_item in accessRecord[key]:
                        imageType=None
                        if(image_item["imageType"]=="CAPTURED_IMAGE"):
                            imageType="FACE"
                        elif(image_item["imageType"]=="SCENE_IMAGE"):
                            imageType="SCENE"
                        if(imageType in("FACE","SCENE")):
                            d=datetime.datetime.now()
                            #相对目录
                            relative_path = os.path.join(imageType,str(d.year),str(d.month).zfill(2),str(d.day).zfill(2))
                            #文件名
                            file_name='{}.jpg'.format(uuid.uuid1())

                            #保存的完整路径包括文件名
                            save_path=os.path.join(self.save_image_path,relative_path,file_name)
                            #文件夹路径 不包括文件名
                            folder_path=os.path.join(self.save_image_path,relative_path)
                            try:
                                if os.path.exists(folder_path) is not True:
                                    os.makedirs(folder_path)
                            except:
                                pass
                            if imageType=="FACE":
                                base64_face_image = base64.b64encode(image_item["image"])
                                base64_face_image = base64_face_image.decode()
                            with open(save_path,'wb+') as f:
                                f.write(image_item["image"])
                            
                            path=os.path.join(relative_path,file_name)
                            os.chmod(save_path, stat.S_IRWXU|stat.S_IRWXG|stat.S_IRWXO)

                            result[imageType]=path

                elif(key=="passed"):
                    result[key]=1 if accessRecord[key]["--record"]=="NewAccessGranted" else 0
                elif(key in("devId","timestamp","personId","similarity","cardNumber")):
                    result[key]=accessRecord.get(key)
            if(float(result["similarity"])<self.threshold):
                result["cardNumber"]=None
            if(result["cardNumber"]==None):
                result[key]=1
            else:
                result[key]=0
            print(result)
            with self.lock_record:
                try:
                    self.access_records_call_Back(self,result,base64_face_image)
                except:
                    pass
            self.lock_record.release()
    
    def __th_access_records(self):
        """
            获取通行记录线程
        """
        while True:
            if(self.exit):
                break
            self.lock.acquire()
            self.__conn_access_records_http20()
            self.access_records_last_time=time.time()
            self.lock.release()
            if(self.access_records_http20==None):
                return False
            response=self.access_records_http20["response"]
            conn=self.access_records_http20["conn"]
            while True:
                if(self.exit):
                    break
                conn.connect()
                try:
                    package_len = int.from_bytes(response.read(4), byteorder='big')
                    self.access_records_last_time=time.time()
                except ConnectionAbortedError as e:
                    package_len=0
                except Exception as ee:
                    package_len=0
                try:
                    if(package_len>0):
                        data = response.read(package_len)
                        self.access_records_last_time=time.time()
                        print("读到过人记录")
                        self.__handle_access_records(data)
                    else:
                        break
                except Exception as e:
                    break
            time.sleep(3/1000)
                
    def access_records(self,callBack):
        """
            启动读取过人记录线程
        """
        if(self.access_records_thread==None):
            self.access_records_call_Back=callBack
            self.access_records_thread=Thread(target=self.__th_access_records)
            self.access_records_thread.start()

    def open_door(self,doorStatus=OPEN_CLOSE,electricGateInterval=500):
        """
        开门
        """
        path="/remote_door_control"
        request_obj = {
                    "doorStatus":doorStatus,
                    "electricGateInterval":electricGateInterval
            }
        # 序列化
        with io.BytesIO() as f:
            util.write_avro(util.OPEN_DOOR_SCHEMA, f, request_obj)
            body = f.getvalue()
        for _ in range(3):
            try:
                http20=self.__http20(path,self.__public_headers(),"POST",body)
                break
            except Exception as e:
                self.state=False
                self.state_text=e
                self.__log_callBack(self,"[门控制]{},{}".format(doorStatus,e),self.LOG_ERROR)
                self.re_conn_number+=1
                time.sleep(self.re_conn_number*3)

        if(http20==None):
            return False
        http20["response"].close()
        http20["conn"].close()
        return True
    
    def persons_version(self):
        """
        搞不懂干嘛用
        """
        path="/persons_version"
        headres=self.__public_headers()
        http20=self.__http20(path,headres)
        response=http20["response"]
        write_schema=b','.join(response.headers.get('x-kassq-sg-schema')).decode()
        # write_schema=b'{"type":"record","name":"PersonChecksum","namespace":"com.securitygate.http2.pojo","fields":[{"name":"personChecksum","type":{"type":"string","avro.java.string":"String"}}]}'
        package_len = int.from_bytes(response.read(4), byteorder='big')
        if(package_len>0):
            data = response.read(package_len)
            with io.BytesIO(data) as f:
                datum = util.read_avro(write_schema,write_schema, f)

    def settings(self):
        path="/settings"
        headres=self.__public_headers()
        http20=self.__http20(path,headres)
        response=http20["response"]
        #write_schema=b','.join(response.headers.get('x-kassq-sg-schema')).decode()
        write_schema=b'{ "namespace": "com.oliveapp.libhttpservice.avro", "type": "record", "name": "DeviceInfo", "fields": [ { "name": "productVersion", "type": "string" },{ "name": "appVersion", "type": "string" },{ "name": "deviceType", "type": "string" },{ "name": "systemVersion", "type": "string" },{ "name": "targetVersion", "type": ["null", "string"] } ] }'
        package_len = int.from_bytes(response.read(4), byteorder='big')
        if(package_len>0):
            data = response.read(package_len)
            with io.BytesIO(data) as f:
                datum = util.read_avro(write_schema,write_schema, f)
                print(datum)

    def display_image(self,imageName,textContent,displayMillis=20000):
        path="/display_image"
        request_obj = {
            "imageName":str(imageName),
            "displayMillis":displayMillis,
            "TextOption":[
                {
                    "enableText":True,
                    "textContent":textContent,
                    "textColor":int("0x7F0000FF",16),
                    "textSizeSp":50,
                    "textCenterPosXDp":360,
                    "textCenterPosYDp":320,
                    "meta":r'{"time_sync_switch":true,"time_server":"2.android.pool.ntp.org"}',
                }
            ]
        }
        # 序列化
        with io.BytesIO() as f:
            util.write_avro(util.DISPLAY_IMAGE_SCHEMA, f, request_obj)
            body = f.getvalue()
        for _ in range(3):
            try:
                http20=self.__http20(path,self.__public_headers(),"POST",body)
                break
            except Exception as e:
                self.state=False
                self.state_text=e
                print(e)
                self.__log_callBack(self,"[显示图片]{}".format(e),self.LOG_ERROR)
                self.re_conn_number+=1
                time.sleep(self.re_conn_number*3)
            

        if(http20==None):
            return False
        http20["response"].close()
        http20["conn"].close()
        return True

    def save_person(self,id,cardNo,displayPic,operationType,pname="人员"):
        path="/persons"
        # picChecksum=hashlib.sha256(displayPic).hexdigest()
        request_obj = {
                    "operationType":operationType,
                    "person":{
                        "id":str(id),
                        "cardNo":str(cardNo),
                        "displayInfo":pname,
                        "displayPic":displayPic,
                        # "picChecksum":picChecksum,
                    }
            }
        # 序列化
        with io.BytesIO() as f:
            util.write_avro(util.CREATE_PERSON_SCHEMA, f, request_obj)
            body = f.getvalue()
        headers=self.__public_headers()
        headers.update({
            "include_picture":"true",
            "face_vx":"face_v2",
        })
        while True:
            if(self.exit):
                break
            try:
                http20=self.__http20(path,headers,"POST",body,timeout=15)
                print("添加人员成功")     
                break  
            except RequestError as e_request:
                raise e_request
            except Exception as e:
                self.state=False
                self.state_text=e
                self.__log_callBack(self,"[{}人员]{}".format(operationType,e),self.LOG_ERROR)
                self.re_conn_number+=1
                time.sleep(self.re_conn_number*3)

        if(http20==None):
            return False
        http20["response"].close()
        http20["conn"].close()
        return True